/*
 * Copyright 2013 Agustina Martinez-Garcia

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package uk.ljmu.qudexapps.xmllib.services;

import java.util.List;

import org.apache.log4j.Logger;
import org.apache.xmlbeans.XmlException;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.igfay.jfig.JFigException;

import fedora.fedoraSystemDef.foxml.DatastreamType;
import fedora.fedoraSystemDef.foxml.DatastreamVersionType;
import fedora.fedoraSystemDef.foxml.DigitalObjectDocument;
import fedora.fedoraSystemDef.foxml.StateType;

import uk.da.dext.QudexDocument;
import uk.da.dext.datastructs.CategoryStruct;
import uk.da.dext.datastructs.CodeStruct;
import uk.da.dext.datastructs.MemoStruct;
import uk.da.dext.exceptions.QudexException;
import uk.da.dext.genxml.QudexManagement;

import uk.ljmu.fedoraifz.restrequests.FedoraOperationsAPIM;
import uk.ljmu.fedoraifz.restrequests.FedoraOperationsAPIMImpl;
import uk.ljmu.metadata.schemasutilities.SchemasProcessing;
import uk.ljmu.metadata.utilities.MetadataUtilities;
import uk.ljmu.qudexapps.xmllib.exceptions.FoxmlException;
import uk.ljmu.qudexapps.xmllib.model.FoxmlBeans;
import uk.ljmu.qudexapps.xmllib.utilities.Constants;
import uk.ljmu.qudexapps.xmllib.utilities.IngestionResults;

public class FedoraManagement {
    
    private static Logger log = Logger.getLogger(FedoraManagement.class);

    /**
     * Fedora communication Object instance to handle communication
     * requests/responses from the repository
     */
    private FedoraOperationsAPIM fedoraObject;
    
    /**
     * Admin Fedora username - Extracted from configuration
     */
    private String fedoraUser;

    private List<IngestionResults> objResults;

    /**
     * @return the objResults
     */
    public List<IngestionResults> getObjResults() {
        return objResults;
    }

    /**
     * @param objResults the objResults to set
     */
    public void setObjResults(List<IngestionResults> objResults) {
        this.objResults = objResults;
    }

    /**
     * @param fedoraObject the fedoraObject to set
     */
    public void setFedoraObject(FedoraOperationsAPIM fedoraObject) {
	this.fedoraObject = fedoraObject;
    }

    /**
     * @return the fedoraObject
     */
    public FedoraOperationsAPIM getFedoraObject() {
	return fedoraObject;
    }

    /**
     * @param fedoraUser the fedoraUser to set
     */
    public void setFedoraUser(String fedoraUser) {
	this.fedoraUser = fedoraUser;
    }

    /**
     * @return the fedoraUser
     */
    public String getFedoraUser() {
	return fedoraUser;
    }
    
    public FedoraManagement() throws JFigException {
	this.fedoraObject = new FedoraOperationsAPIMImpl();
    }
    
    public FedoraManagement(FedoraOperationsAPIM fedObj) {
	this.fedoraObject = fedObj;
    }

    public FedoraManagement(String user, String password) {
	this.fedoraObject = new FedoraOperationsAPIMImpl(user, password);
    }

    /**
     * Performs Fedora APIM call to ingest a new Foxml object in the repository
     * 
     * @param namespace
     *            PID namespace obtained from collection
     * @param contents
     *            Foxml contents of the object
     * @param foxmlPid
     *            the object's assigned PID
     * @throws JFigException
     */
    public String ingestObjectFedora(String label, String namespace, String contents,
	    String foxmlPid) throws FoxmlException, JFigException {
	StringBuilder sb = new StringBuilder();
	FedoraOperationsAPIM fedObj;

	if (this.fedoraObject != null)
	    fedObj = this.fedoraObject;
	else
	    fedObj = new FedoraOperationsAPIMImpl();

	/*
	 * AMG- Ingest parameters: object PID, Foxml XML contents, mimetype,
	 * object label, log message, Object schema version, object format
	 * storage (UTF-8, ISO), object namespace, object owner, object status
	 * (active, deleted, inactive) and ignore mimetype parameter
	 */
	String resultPid = fedObj.createObject(foxmlPid, (String) contents,
		Constants.DEFAULT_MIMETYPE,
		label,
		"Digital Object Creation",
		MetadataUtilities.NSMAP.get("foxml1.1"), Constants.ISO_8859_1,
		namespace, fedoraUser, StateType.A.toString(), "");

	if (resultPid != null && !resultPid.equals("")
		&& resultPid.contains(Constants.PID_DELIMITER)) {

	    return resultPid;
	} else {
	    sb.append("Object not ingested, error in ingestObjectFedora: ");
	    sb.append(resultPid);
	    log.fatal(sb.toString());
	    throw new FoxmlException(sb.toString());
	}
    }
    /**
     * 
     * @param pid
     * @param removeCompletely
     * @return
     */
    public String deleteObjectFedora(String pid, boolean removeCompletely) {
	// At the moment temporarily it just marks the object as deleted without
	// physically removing it
	String res = "";
	if (removeCompletely) {
	    res = this.fedoraObject.deleteObject(pid);
	    //if (res == null) {
		//this.addFedoraResultsMessage(pid,
		//	"Error while deleting object with PID: " + pid
		//		+ ". Contact Administrator.");
	    //}
	} else {
	    res = this.fedoraObject.modifyObject(pid, "", this.fedoraUser,
		    StateType.D.toString(), "Marking object as deleted", "",
		    "", Constants.DEFAULT_MIMETYPE);
	    return res;
	}
	return res;
    }
    /**
     * 
     * @param collectionPid
     * @return
     */
    public boolean isExistingQudexCollection(String collectionPid) {
	String res = this.getFedoraObject().getDatastream(collectionPid, Constants.QUDEX_INSTANCE_DS_NAME, "", "xml", false);
	if (!res.startsWith(Constants.XML_UTF_VERSION_HEADER))
	    return false;
	else return true;
    }
    
    /**
     * 
     * @return
     */
    public String updateObjectFedora(FoxmlBeans foxml) {
	StringBuilder sb = new StringBuilder();
	String result = foxml.getFoxmlDoc().getDigitalObject()
		.getPID();
	sb.append(Constants.XML_VERSION_HEADER);
	sb.append(foxml.getFoxmlDoc().toString());

	DatastreamType[] dsArray = foxml.getFoxmlDoc()
		.getDigitalObject().getDatastreamArray();

	for (DatastreamType datastream : dsArray) {
	    DatastreamVersionType dstype = datastream
		    .getDatastreamVersionArray(0);
	    String requestres = this.getFedoraObject().modifyDatastream(foxml
		    .getFoxmlDoc().getDigitalObject().getPID(), datastream
		    .getID(), dstype.getXmlContent().toString(), "", dstype
		    .getLABEL(), true, datastream.getSTATE().toString(), "",
		    "", "", dstype.getMIMETYPE(),
		    "Updating collection datastream", false, "");
	    if (requestres == null) {
		// ERROR
		result = null;
	    }
	}

	return result;

    }
    
    /**
     * 
     * @return
     */
    public boolean addQudexInstanceToFedoraCollection(FoxmlBeans foxml, List<String> qudexCollectionsPIDs, QudexDocument qudexInstance) {
	for (String parentPid : qudexCollectionsPIDs) {
	    foxml.initFoxmlDocumentObject();
	    String foxmlStr = this.getFedoraObject().getObjectXML(parentPid);
	    if (foxmlStr.contains(Constants.XML_UTF_VERSION_HEADER)) {
		try {
		    foxml.setFoxmlDoc(DigitalObjectDocument.Factory
			    .parse(foxmlStr));
		} catch (XmlException e1) {
		    log.fatal(e1.getMessage());
		    return false;
		}
		try {
		    foxml.addDatastream(Constants.QUDEX_INSTANCE_DS_NAME,
			    DatastreamType.CONTROLGROUP.X,
			    "Qudex XML Instance", StateType.A,
			    qudexInstance.toString(),
			    Constants.DEFAULT_MIMETYPE, true, false); /*Non versionable*/
		    return true;
		} catch (FoxmlException e) {
		    log.fatal(e.getMessage());
		    return false;
		}
	    }
	}
	return false;
    }
    /**
     * Remove all the object in the specified list
     * 
     * @param mylist
     *            list with PIDs of the objects to remove
     */
    public void deleteAllFedoraDocuments(List<String> mylist) {
	for (String pid : mylist) {
	    String res = this.deleteObjectFedora(pid, true);
	    if (res == null) {
		log.error("Error while deleting object with PID: " + pid);
		this.addFedoraResultsMessage(pid,
			"Error while deleting object with PID: " + pid
				+ ". Contact Administrator.");
	    }
	}
    }
    
    public boolean deleteQudexCollection(String collectionPid, boolean removeCompletely) {
	try {
	    FedoraMulgaraService mulgService = new FedoraMulgaraServiceImpl();
	    //Remove Collection
	    String res = this.deleteObjectFedora(collectionPid, removeCompletely);
	    if (res == null || res.trim().equals(collectionPid)) {
		this.addFedoraResultsMessage(collectionPid,
			"Error while deleting object with PID: " + collectionPid
				+ ". Object not present.");
		return false;
	    }
	    if (removeCompletely) {
		List<String> depFiles = mulgService.retrieveDependantQudexDocuments(collectionPid);
		if (depFiles == null) {
		    this.addFedoraResultsMessage(collectionPid,
				"Error while deleting object with PID: " + collectionPid
					+ ". Contact Administrator.");
		    return false;
		}
		for (String pid : depFiles){
		    res = this.deleteObjectFedora(pid, removeCompletely);
		    if (res == null) {
			this.addFedoraResultsMessage(pid,
				"Error while deleting dependant Document with PID: " + pid
					+ ". Contact Administrator.");
			return false;
		    }
		}
	    }
	    return true;
	} catch (JFigException e) {
	    log.error("Error in deleteQudexCollection: ", e);
	    return false;
	}
    }
    
    /**
     * Parses the categories map to add all the relationships between categories
     * for a given qudex study. All the categories datastreams are already
     * present in the categories object.
     * 
     * @return true if the datastream is created succesfully, false otherwise
     */
    @SuppressWarnings("unchecked") boolean createRelsIntCategoryDatastream(String pid, FoxmlBeans foxml, QudexManagement qudexManager) {
	Document doc = DocumentHelper.createDocument();
	boolean resultsOK;
	doc = SchemasProcessing.constructRelsIntDocument();

	if (doc != null) {
	    for (CategoryStruct category : (List<CategoryStruct>) qudexManager
		    .getCategoryManager().getElementsList()) {
		if (category.findDocumentRef(pid)) {
		    for (String parent : category.getImmParents()) {
			resultsOK = SchemasProcessing
				.addRelsIntRecord(
					"rel",
					doc,
					generateDatastreamPid(pid, category
						.getCategoryDisplayLabel()),
					"isMemberOf",
					generateDatastreamPid(pid, parent),
					uk.ljmu.metadata.utilities.Constants.RDFS_TYPE_RES);
			if (!resultsOK)
			    return false;
			// Added bi-directional relationship
			resultsOK = SchemasProcessing
				.addRelsIntRecord(
					"rel",
					doc,
					generateDatastreamPid(pid, parent),
					"hasMember",
					generateDatastreamPid(pid, category
						.getCategoryDisplayLabel()),
					uk.ljmu.metadata.utilities.Constants.RDFS_TYPE_RES);
			if (!resultsOK)
			    return false;
		    }
		}
	    }
	    // AMG Call Ingest Datastream Method
	    try {
		foxml.addDatastream("RELS-INT",
			DatastreamType.CONTROLGROUP.X, "RELS-INT Datastream",
			StateType.A, doc.asXML(), Constants.DEFAULT_MIMETYPE,
			true, false); /*Non versionable*/
	    } catch (FoxmlException e) {
		log.fatal("Error creating category datastream: "
			+ e.getMessage());
		return false;
	    }
	    return true;
	} else {
	    return false;
	}
    }
    /**
     * Adds Categories datastreams to a already stored Fedora Qudex CATEGORY
     * Object
     * 
     * @return true if the datastreams were added successfully, false otherwise
     */
    @SuppressWarnings("unchecked") boolean generateCategoryDatastreams(String pid, FoxmlBeans foxmlBeans, QudexManagement qudexManager) {
	StringBuilder sb = new StringBuilder();
	boolean resultsOk = true;
	// AMG - (1) Create Qudex XML snippet for each category
	for (CategoryStruct category : (List<CategoryStruct>) qudexManager.getCategoryManager()
		.getElementsList()) {
	    try {
		// AMG - 2-03-11, only add category snippet if is associated
		// with this document
		if (category.findDocumentRef(pid)) {
		    String qudexCatSnippet = QudexManagement
			    .generateCategorySnippet(category
				    .getCategoryXmlId(), category
				    .getDocumentRefsXmlIds(), category
				    .getImmChildren(), category
				    .getCategoryLabel(), category
				    .getCategoryDisplayLabel(), category
				    .getCategoryLanguage());
		    if (qudexCatSnippet != null) {
			// AMG - (2) create category datastream
			sb.append(Constants.XML_VERSION_HEADER);
			sb.append(qudexCatSnippet);
			foxmlBeans.addDatastream(this.createCategoryDSName(category.getCategoryLabel()),
				DatastreamType.CONTROLGROUP.X,
				"Category datastream", StateType.A, sb
					.toString(),
				Constants.DEFAULT_MIMETYPE, true, true); /*Versionable*/
			// AMG - Add Category Snippet if the ds has been created
			resultsOk = qudexManager
				.addCategory(sb.toString());
			if (!resultsOk)
			    return false;
			sb.delete(0, sb.toString().length());
		    } else {
			log
				.fatal("generateCategoryDatastreams, error generating category xml snippet for category: "
					+ category.getCategoryDisplayLabel());
			return false;
		    }
		}
	    } catch (QudexException e) {
		log.fatal(e.getMessage());
	    } catch (FoxmlException e) {
		log.fatal("Error creating category datastream: "
			+ e.getMessage());
		return false;
	    }
	}

	return true;
    }
    
  //QUDEX CATEGORIES, MEMOS, CODES MANAGE DATASTREAMS
    /**
     * 
     * @param item
     * @return
     */
    @SuppressWarnings("unchecked")
    public boolean generateMemoDatastreams(QudexManagement qudexManager, FoxmlBeans foxmlBeans) {
	StringBuilder sb = new StringBuilder();
	boolean resultsOk = true;
	// AMG - (1) Create Qudex XML snippet for each code
	for (MemoStruct memo : (List<MemoStruct>) qudexManager.getMemoManager()
		.getElementsList()) {
	    try {
		if (memo.findDocumentRef(foxmlBeans.getFoxmlDoc()
			.getDigitalObject().getPID())) {
		    String qudexMemoSnippet = QudexManagement
			    .generateMemoSnippet(memo.getMemoXmlId(), memo
				    .getMemoLabel(), memo.getMemoText(), memo
				    .getMemoLanguage(), memo.getDocumentRefs());
		    if (qudexMemoSnippet != null) {
			// AMG - (2) create code datastream
			sb.append(Constants.XML_VERSION_HEADER);
			sb.append(qudexMemoSnippet);
			foxmlBeans.addDatastream(this.createMemoDSName(memo.getMemoLabel()),
				DatastreamType.CONTROLGROUP.X,
				"Memo datastream", StateType.A, sb.toString(),
				Constants.DEFAULT_MIMETYPE, true, false); /*Non versionable*/
			// AMG - Add Memo Snippet if the datastream has been
			// created
			resultsOk = qudexManager.addMemo(sb.toString());
			if (!resultsOk)
			    return false;
			sb.delete(0, sb.toString().length());
		    } else {
			log
				.fatal("generateCategoryDatastreams, error generating category xml snippet for category: "
					+ memo.getMemoLabel());
			return false;
		    }
		}
	    } catch (QudexException e) {
		log.fatal(e.getMessage());
		return false;
	    } catch (FoxmlException e) {
		log.fatal("Error creating memo datastream: " + e.getMessage());
		return false;
	    }
	}

	return true;
    }
    
    
    /**
     * 
     * @return
     */
    @SuppressWarnings("unchecked")
    public boolean generateCodeDatastreams(QudexManagement qudexManager, FoxmlBeans foxml) {
	StringBuilder sb = new StringBuilder();
	boolean resultsOk = true;
	// AMG - (1) Create Qudex XML snippet for each code
	for (CodeStruct code : (List<CodeStruct>) qudexManager.getCodeManager()
		.getElementsList()) {
	    String qudexCodeSnippet;
	    try {
		if (code.findDocumentRef(foxml.getFoxmlDoc()
			.getDigitalObject().getPID())) {
		    qudexCodeSnippet = QudexManagement.generateCodeSnippet(code
			    .getCodeXmlId(), code.getCodeLabel(), code
			    .getCodeDisplayLabel(), code.getCodeAuthority(),
			    code.getCodeLanguage());
		    if (qudexCodeSnippet != null) {
			// AMG - (2) create code datastream
			sb.append(Constants.XML_VERSION_HEADER);
			sb.append(qudexCodeSnippet);
			foxml.addDatastream(this.createCodeDSName(code.getCodeLabel()),
				DatastreamType.CONTROLGROUP.X,
				"Code datastream", StateType.A, sb.toString(),
				Constants.DEFAULT_MIMETYPE, true, true); /*Versionable*/
			resultsOk = qudexManager.addCode(sb.toString());
			if (!resultsOk)
			    return false;
			sb.delete(0, sb.toString().length());
		    } else {
			log
				.fatal("generateCategoryDatastreams, error generating code xml snippet for code: "
					+ code.getCodeDisplayLabel());
			return false;
		    }
		}
	    } catch (QudexException e1) {
		log.fatal(e1.getMessage());
		e1.printStackTrace();
		return false;
	    } catch (FoxmlException e) {
		log.fatal("Error creating code datastream: " + e.getMessage());
		return false;
	    }
	}

	return true;
    }
    
    /**
     * Creates the Categories FOXML object associated with a Qudex Qualitative
     * Collection
     * 
     * @param pid fedora category identifier
     */
    public boolean createCategoryDatastreams(String pid, QudexManagement qudexManager) {
	StringBuilder sb = new StringBuilder();
	boolean resultsOK = false;
	String foxmlXml = this.getFedoraObject().getObjectXML(pid);
	FoxmlBeans temporaryFoxml = new FoxmlBeans(foxmlXml, this.getFedoraObject());
	if (temporaryFoxml.getFoxmlDoc() == null)
	    return false;
	resultsOK = this.generateCategoryDatastreams(pid, temporaryFoxml, qudexManager);
	if (!resultsOK) {
	    sb.append("Error while adding Categories datastreams to Category Fedora object.");
	    log.fatal(sb.toString());
	    return false;
	}
	// AMG - Manage RELS-INT ds, add hierarchies relationships for categories
	resultsOK = this.createRelsIntCategoryDatastream(pid, temporaryFoxml, qudexManager);
	if (!resultsOK)
	    return false;

	return true;
    }
    
    /**
     * Creates the Codes datastreams included in the Fedora Document object
     * associated with a Qudex Qualitative Document
     * 
     * @return
     * 
     */
    public boolean createCodeDatastreams(QudexManagement qudexManager, FoxmlBeans foxml) {
	StringBuilder sb = new StringBuilder();
	boolean resultsOK = false;

	// AMG - (4) Manage datastreams creation
	resultsOK = generateCodeDatastreams(qudexManager, foxml);
	if (!resultsOK) {
	    sb
		    .append("Error while adding codes datastreams to Category Fedora object.");
	    log.fatal(sb.toString());
	    return false;
	}
	return true;
    }

    /**
     * Creates the Memos datastreams included in the Fedora Document object
     * associated with a Qudex Qualitative Document
     * 
     * @return
     * 
     */
    public boolean createMemoDatastreams(QudexManagement qudexManager, FoxmlBeans foxml) {
	StringBuilder sb = new StringBuilder();
	boolean resultsOK = false;

	// AMG - (4) Manage datastreams creation
	resultsOK = generateMemoDatastreams(qudexManager, foxml);
	if (!resultsOK) {
	    sb
		    .append("Error while adding memos datastreams to Memo Fedora object.");
	    log.fatal(sb.toString());
	    return false;
	}
	return true;
    }
   
    // PRIVATE METHODS

    private String createCategoryDSName(String catLabel) {
	return Constants.CAT_LABEL_PREFIX + catLabel.replace(" ", "_");
    }
    
    private String createMemoDSName(String memoLabel) {
	return Constants.MEMO_LABEL_PREFIX + memoLabel.replace(" ", "_");
    }
    
    private String createCodeDSName(String codeLabel) {
	return Constants.CODE_LABEL_PREFIX + codeLabel.replace(" ", "_");
    }
    
    /**
     * 
     * @param displayLabel
     * @return
     */
    private String generateDatastreamPid(String pid, String displayLabel) {
	if (displayLabel.contains(" ")) {
	    displayLabel = displayLabel.replace(" ", "_");
	}
	return MetadataUtilities.OBJECTID
		+ pid
		+ "/" + displayLabel;
    }

    public void addFedoraResultsMessage(String identifier, String message) {
	// RESULTS OBJECT CREATION AND PARSE OF INGESTION RESULTS
	IngestionResults obj = new IngestionResults(identifier, message);
	// ADD INFORMATION TO THE ARRAY OF RESULTS
	this.objResults.add(obj);
    }
}
